package backend;

import java.util.*;
import java.util.List;
import java.io.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.*;

public class TimeLineAnalyzer {

	private Integer[] timeline;
	private Hashtable<String, ArrayList<ActionPatch>> patches;
	
	public TimeLineAnalyzer(){
		this.timeline = null;
		this.patches = null;
	}
	
	/**
	 * Read the timeline file into a timeline object.
	 * 
	 * @param filePath
	 */
	public boolean readTimeLine(String filePath){
		File f = new File(filePath);
		try {
			Scanner scn = new Scanner(f);
			int count = 0;
			while(scn.hasNextLine()){
				count ++;
				scn.nextLine();
			}
			scn.close();
			
			this.timeline = new Integer[count];
			scn = new Scanner(f);
			int index = 0;
			while(scn.hasNextLine()){
				try{
					Integer val = Integer.parseInt(scn.nextLine());
					this.timeline[index] = val;
				}catch(NumberFormatException e){
					this.timeline[index] = null; 
				}
				index ++;
			}
			scn.close();
			this.collectActionPatches();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	/**
	 * Get the median value of the integer list.
	 * @param list
	 * @return
	 */
	private Integer getMedian(Integer[] list){
		Hashtable<Integer, Integer> counts = 
			new Hashtable<Integer, Integer>();
		int size = list.length;
		for (int i = 0; i < size; i ++){
			Integer ele = list[i];
			if (ele != null){
				Integer count = counts.get(ele);
				if (count != null){
					counts.put(ele, count+1);
				}else{
					counts.put(ele, 1);
				}
			}
		}
		Enumeration<Integer> enu = counts.keys();
		Integer result = null;
		int maxCount = 0;
		while(enu.hasMoreElements()){
			Integer temp = enu.nextElement();
			int tmpCount = counts.get(temp);
			if (tmpCount > maxCount){
				maxCount = tmpCount;
				result = temp;
			}
		}
		return result;
	}

	/**
	 * Collect the patches.
	 */
	private void collectActionPatches(){
		this.patches = new Hashtable<String, ArrayList<ActionPatch>>();
		if (this.timeline != null){
			Integer median = this.getMedian(this.timeline);
			ActionPatch globalPatch = new ActionPatch();
			globalPatch.setFrames(1, this.timeline);
			ArrayList<Integer[]> denseTL = globalPatch.getKeyLabels();
			if (denseTL != null){
				Iterator<Integer[]> itr = denseTL.iterator();
				int patchStart = 0;
				int patchEnd = 0;
				while(itr.hasNext()){
					Integer[] fl = itr.next();
					Integer frameIndex = fl[0];
					Integer frameLabel = fl[1];
					// reaching a median frame
					if (frameLabel == median){
						if (patchStart != 0){
							patchEnd = frameIndex;
							this.insertAPatch(patchStart, patchEnd);
							patchStart = 0;
							patchEnd = 0;
						}
					}
					// reaching a non-median frame
					else{
						// if the patch start is not specified yet, and the
						// current frame is not the median, then it must be the
						// second start frame (when there is a space for a 
						// median frame in front) or the first frame of another
						// action patch
						if (patchStart == 0){
							// if it is the first frame
							patchStart = frameIndex;
							// if it can be the second frame
							if (patchStart > 1){
								patchStart -= 1;
							}
						}
						
						// if this is the end of the timeline
						else if (!itr.hasNext()){
							patchEnd = frameIndex;
							this.insertAPatch(patchStart, patchEnd);
							patchStart = 0;
							patchEnd = 0;
						}
					}
				}
			}
//			System.out.print(this.printPatches());
		}
	}
	
	/**
	 * Print the patches.
	 * @return
	 */
	public String printPatches(){
		String result = "----------\n";
		if (this.patches!= null){
			Enumeration<String> enu = this.patches.keys();
			while(enu.hasMoreElements()){
				String key = enu.nextElement();
				result += key+":\n";
				ArrayList<ActionPatch> subPatches =
					this.patches.get(key);
				Iterator<ActionPatch> itr = 
					subPatches.iterator();
				while(itr.hasNext()){
					ActionPatch patch = itr.next();
					result+="\t"+patch.toString()+"\n";
				}
			}
		}
		return result;
	}

	/**
	 * Build the tree display based on the hashtable of patches.
	 */
	public void buildTree(Tree tree){
		if (this.patches != null){
			tree.removeAll();
			
			Hashtable<Integer, Hashtable<Integer, ArrayList<String>>> treeMap;
			treeMap = new Hashtable<Integer, Hashtable<Integer, ArrayList<String>>>();
			int median = this.getMedian(this.timeline);
			Enumeration<String> enu = this.patches.keys();
			while(enu.hasMoreElements()){
				String key = enu.nextElement();
				ArrayList<ActionPatch> subPatches = this.patches.get(key);
				ActionPatch samplePatch = subPatches.get(0);
				int dir = samplePatch.getDirection(median);
				int magnitude = samplePatch.getMagnitude(median);
				Hashtable<Integer, ArrayList<String>> subTreeMap =
					treeMap.get(new Integer(dir));
				if (subTreeMap == null){
					subTreeMap = new Hashtable<Integer, ArrayList<String>>();
					treeMap.put(new Integer(dir), subTreeMap);
				}
				ArrayList<String> subSubTreeMap = 
					subTreeMap.get(new Integer(Math.abs(magnitude)));
				if (subSubTreeMap == null){
					subSubTreeMap = new ArrayList<String>();
					subTreeMap.put(new Integer(Math.abs(magnitude)), subSubTreeMap);
				}
				subSubTreeMap.add(key);
			}
			
			String[] firstLevelItemNames = 
				new String[]{"towards left", "towards both", "towards right"};
			List<Integer> list1 = Collections.list(treeMap.keys());
			Collections.sort(list1);
			Iterator<Integer> itr1 = list1.iterator();
			while(itr1.hasNext()){
				Integer dir = itr1.next();
				Hashtable<Integer, ArrayList<String>> subTreeMap =
					treeMap.get(dir);
				TreeItem item1 = new TreeItem(tree, SWT.NONE);
				item1.setText(firstLevelItemNames[dir+1]);
				List<Integer> list2 = Collections.list(subTreeMap.keys());
				Collections.sort(list2);
				Iterator<Integer> itr2 = list2.iterator();
				while(itr2.hasNext()){
					Integer mag = itr2.next();
					TreeItem item2 = new TreeItem(item1, SWT.NONE);
					item2.setText("Magnitude: "+mag);
					ArrayList<String> subSubTreeMap= subTreeMap.get(mag);
					Collections.sort(subSubTreeMap);
					Iterator<String> itr3 = subSubTreeMap.iterator();
					while(itr3.hasNext()){
						String klbls = itr3.next();
						TreeItem item3 = new TreeItem(item2, SWT.NONE);
						item3.setText(klbls);
						ArrayList<ActionPatch> subPatches =
							this.patches.get(klbls);
						Collections.sort(subPatches);
						Iterator<ActionPatch> itr4 = subPatches.iterator();
						while(itr4.hasNext()){
							ActionPatch patch = itr4.next();
							TreeItem item4 = new TreeItem(item3, SWT.NONE);
							item4.setText(patch.toString());
							item4.setData("start", patch.getStartFrame());
							item4.setData("end", patch.getEndFrame());
						}
					}
				}
			}
		}
	}
	
	/**
	 * Insert a patch to the collection.
	 * 
	 * @param startFI
	 * @param endFI
	 */
	private void insertAPatch(int startFI, int endFI){
		if (this.patches == null){
			this.patches = new Hashtable<String, ArrayList<ActionPatch>>();
		}
		if (startFI > 0 && endFI > startFI){
			int length = endFI-startFI+1;
			Integer[] lbls = new Integer[length];
			for(int i = 0; i < length; i ++){
				lbls[i] = this.timeline[startFI-1+i];
			}
			ActionPatch patch = new ActionPatch();
			patch.setFrames(startFI, lbls);
			String key = patch.getKey();
			ArrayList<ActionPatch> members = this.patches.get(key);
			if (members == null){
				members = new ArrayList<ActionPatch>();
			}
			members.add(patch);
			this.patches.put(key, members);
		}
	}
	
	/**
	 * An action patch.
	 * 
	 * @author dongxi
	 *
	 */
	public class ActionPatch implements Comparable<ActionPatch>{
		private int startingFrameIndex;
		private Integer[] labels;
		private ArrayList<Integer[]> keyLabels;
		
		public ActionPatch(){
			this.startingFrameIndex = -1;
			this.labels = null;
			this.keyLabels = null;
		}
		
		/**
		 * Assign the frames corresponding to the action patch.
		 * @param sfi
		 * @param lbls
		 */
		public void setFrames(int sfi, Integer[] lbls){
			if (lbls != null && sfi > 0){
				this.startingFrameIndex = sfi;
				this.labels = lbls;
				this.extractKeyFrames();
			}
		}
		
		public int getStartFrame(){
			return this.startingFrameIndex;
		}
		
		public int getEndFrame(){
			if (this.startingFrameIndex < 0 || 
					this.labels == null){
				return -1;
			}else{
				return this.startingFrameIndex + this.labels.length-1;
			}
		}
		
		public Integer[] getLabels(){
			return this.labels;
		}
		
		public ArrayList<Integer[]> getKeyLabels(){
			return this.keyLabels;
		}
		
		/**
		 * Extract key frames that start a new label compared to the previous
		 * frame.
		 */
		private void extractKeyFrames(){
			if (this.labels != null){
				int size = this.labels.length;
				Integer prevLabel = null;
				this.keyLabels = new ArrayList<Integer[]>();
				for (int i = 0; i < size; i ++){
					Integer frameIndex = i+this.startingFrameIndex;
					Integer frameLabel = this.labels[i];
					if (frameLabel != prevLabel){
						prevLabel = frameLabel;
						this.keyLabels.add(
								new Integer[]{frameIndex, frameLabel});
					}
				}
			}
		}
		
		/**
		 * Get a key consisting of the ordered key labels
		 * @return
		 */
		public String getKey(){
			String result = "";
			Iterator<Integer[]> itr = this.keyLabels.iterator();
			while(itr.hasNext()){
				result += itr.next()[1]+"@";
			}
			return result;
		}
	
		/**
		 * Decide which direction the action is looking towards, based on the 
		 * given median (0 angle) label.
		 * 
		 * @param median
		 * @return	-1: left
		 * 			 1: right
		 * 			 0: both
		 */
		public int getDirection(int median){
			int left = 0;
			int right = 0;
			if (this.keyLabels!= null){
				Iterator<Integer[]> itr = this.keyLabels.iterator();
				while(itr.hasNext()){
					Integer[] fl = itr.next();
					Integer label = fl[1];
					if (label != null){
						if (label < median){
							left = -1;
						}
						else if(label > median){
							right = 1;
						}
					}
				}
			}
			return left+right;
		}

		/**
		 * Decide the magnitude of the action
		 * @param median
		 * @return
		 */
		public int getMagnitude(int median){
			int result = 0;
			if (this.keyLabels != null){
				Iterator<Integer[]> itr = this.keyLabels.iterator();
				while(itr.hasNext()){
					Integer label = itr.next()[1];
					if (label != null){
						int tmp = label - median;
						if (Math.abs(tmp) > Math.abs(result)){
							result = tmp;
						}
					}
				}
			}
			return result;
		}
		
		public String toString(){
			String result = "[";
			if (this.labels!= null){
				int length = this.labels.length;
				for (int i = 0; i < length; i ++){
					result += "("+(this.startingFrameIndex+i)+")";
					result += this.labels[i]+",";
				}
			}
			return result+"]";
		}

		@Override
		public int compareTo(ActionPatch other) {
			int result = 0;
			int thisStart = this.getStartFrame();
			int otherStart = other.getStartFrame();
			if (thisStart > otherStart){
				result = 1;
			}else if (thisStart < otherStart){
				result = -1;
			}
			return result;
		}
	}
}
